Знакомство с системой сборки Cargo
Cargo — это официальная система сборки и пакетный менеджер для Rust. В этой главе мы глубоко изучим возможности Cargo: от создания проектов до управления зависимостями и настройки сложных сборочных процессов.
Что такое Cargo?
Cargo выполняет множество задач в экосистеме Rust:
📦 Пакетный менеджер
Управление внешними библиотеками и зависимостями
🔧 Система сборки
Компиляция, тестирование и создание исполняемых файлов
📋 Менеджер проектов
Создание и организация структуры Rust-проектов
📚 Генератор документации
Автоматическое создание документации из кода
Cargo следует принципу "конвенция вместо конфигурации" — большинство стандартных задач работают "из коробки" без дополнительной настройки.
Типы проектов Cargo
Бинарные проекты (приложения)
Создание приложения:
cargo new my_app
cd my_app
Структура:
my_app/
├── Cargo.toml
├── src/
│ └── main.rs # Точка входа приложения
└── .gitignore
Библиотечные проекты (крейты)
Создание библиотеки:
cargo new my_lib --lib
cd my_lib
Структура:
my_lib/
├── Cargo.toml
├── src/
│ └── lib.rs # Корень библиотеки
└── .gitignore
Смешанные проекты
Проект с библиотекой и приложением:
my_project/
├── Cargo.toml
├── src/
│ ├── main.rs # Бинарное приложение
│ └── lib.rs # Библиотека
├── examples/ # Примеры использования
├── tests/ # Интеграционные тесты
└── benches/ # Бенчмарки производительности
Глубокое изучение Cargo.toml
Файл Cargo.toml — это сердце любого Rust-проекта. Рассмотрим все его возможности:
Секция [package]
- Основные поля
- Дополнительные поля
- Метаданные
[package]
name = "my_awesome_app" # Имя пакета (обязательно)
version = "0.1.0" # Версия (обязательно)
edition = "2021" # Версия языка (обязательно)
authors = ["Ваше Имя <email@example.com>"]
description = "Описание проекта"
license = "MIT" # Лицензия
repository = "https://github.com/user/repo"
homepage = "https://example.com"
documentation = "https://docs.example.com"
readme = "README.md"
keywords = ["web", "api", "server"] # До 5 ключевых слов
categories = ["web-programming"] # Категории на crates.io
[package]
name = "my_app"
version = "1.2.3"
edition = "2021"
rust-version = "1.70" # Минимальная версия Rust
publish = false # Запретить публикацию на crates.io
autobins = true # Автообнаружение бинарных файлов
autoexamples = true # Автообнаружение примеров
autotests = true # Автообнаружение тестов
autobenches = true # Автообнаружение бенчмарков
# Настройки сборки
build = "build.rs" # Скрипт предсборки
exclude = ["docs/", "*.tmp"] # Исключить из пакета
include = ["src/**/*", "Cargo.toml"] # Включить в пакет
[package]
name = "my_app"
version = "0.1.0"
edition = "2021"
[package.metadata]
# Пользовательские данные для инструментов
msrv = "1.70.0"
[package.metadata.docs.rs]
# Настройки для docs.rs
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.bundle]
# Настройки для создания пакетов
name = "My App"
identifier = "com.example.myapp"
Секция [dependencies]
Зависимости — это внешние библиотеки, которые использует ваш проект:
- Простые зависимости
- С функциональностью
- Разные источники
[dependencies]
# Простая зависимость из crates.io
serde = "1.0"
# Точная версия
rand = "=0.8.5"
# Диапазон версий
tokio = ">=1.0, <2.0"
# Совместимые версии (рекомендуется)
reqwest = "^0.11" # эквивалентно ">=0.11.0, <0.12.0"
clap = "~4.1.6" # эквивалентно ">=4.1.6, <4.2.0"
[dependencies]
# Включение конкретных функций
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres"] }
# Отключение стандартных функций
ring = { version = "0.16", default-features = false }
# Опциональные зависимости
uuid = { version = "1.0", optional = true }
chrono = { version = "0.4", optional = true }
[dependencies]
# Из crates.io (стандартный способ)
serde = "1.0"
# Из Git-репозитория
my_lib = { git = "https://github.com/user/my_lib.git" }
other_lib = { git = "https://github.com/user/other_lib", branch = "development" }
tagged_lib = { git = "https://github.com/user/tagged_lib", tag = "v1.0.0" }
commit_lib = { git = "https://github.com/user/commit_lib", rev = "abc123" }
# Локальный путь (для разработки)
local_lib = { path = "../my_local_lib" }
workspace_lib = { path = "libs/workspace_lib" }
# Переименование крейта
actix-rt = { package = "tokio", version = "1.0" }
Другие типы зависимостей
[dev-dependencies]
# Зависимости только для тестов и разработки
assert_cmd = "2.0"
tempfile = "3.0"
criterion = "0.4"
[build-dependencies]
# Зависимости для скриптов сборки (build.rs)
cc = "1.0"
pkg-config = "0.3"
[target.'cfg(windows)'.dependencies]
# Зависимости для конкретных платформ
winapi = { version = "0.3", features = ["winuser"] }
[target.'cfg(unix)'.dependencies]
libc = "0.2"
Управление функциональностью (Features)
Features позволяют условно включать код и зависимости:
Определение функций
[package]
name = "my_lib"
version = "0.1.0"
edition = "2021"
[features]
default = ["json"] # Функции по умолчанию
json = ["dep:serde_json"] # Функция включает зависимость
xml = ["dep:serde_xml_rs"] # Альтернативная функция
full = ["json", "xml", "extra"] # Мета-функция
extra = [] # Пустая функция для условной компиляции
[dependencies]
serde = "1.0"
serde_json = { version = "1.0", optional = true }
serde_xml_rs = { version = "0.5", optional = true }
Использование в коде
// Код включается только если активна функция "json"
#[cfg(feature = "json")]
pub fn parse_json(input: &str) -> Result<serde_json::Value, serde_json::Error> {
serde_json::from_str(input)
}
// Код включается только если активна функция "xml"
#[cfg(feature = "xml")]
pub fn parse_xml(input: &str) -> Result<serde_xml_rs::Value, serde_xml_rs::Error> {
serde_xml_rs::from_str(input)
}
// Код включается если активна любая из функций
#[cfg(any(feature = "json", feature = "xml"))]
pub fn supported_formats() -> Vec<&'static str> {
let mut formats = Vec::new();
#[cfg(feature = "json")]
formats.push("json");
#[cfg(feature = "xml")]
formats.push("xml");
formats
}
Сборка с функциями
# Сборка с функциями по умолчанию
cargo build
# Сборка без функций по умолчанию
cargo build --no-default-features
# Сборка с конкретными функциями
cargo build --features="json xml"
# Сборка со всеми функциями
cargo build --all-features
# Тестирование разных комбинаций функций
cargo test --features="json"
cargo test --features="xml"
cargo test --all-features
Рабочие области (Workspaces)
Workspaces позволяют управлять несколькими связанными пакетами:
Создание workspace
[workspace]
members = [
"server",
"client",
"shared",
"tools/*", # Wildcard для подпапок
]
exclude = [
"legacy", # Исключить из workspace
"experimental/*"
]
resolver = "2" # Использовать новый resolver
[workspace.dependencies]
# Общие зависимости для всех пакетов
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
anyhow = "1.0"
[workspace.metadata]
# Метаданные workspace
rust-version = "1.70"
Структура workspace
my_workspace/
├── Cargo.toml # Корневой манифест workspace
├── Cargo.lock # Единый lock-файл для всего workspace
├── server/ # Пакет сервера
│ ├── Cargo.toml
│ └── src/
├── client/ # Пакет клиента
│ ├── Cargo.toml
│ └── src/
├── shared/ # Общая библиотека
│ ├── Cargo.toml
│ └── src/
└── target/ # Общая папка сборки
Использование в пакетах workspace
[package]
name = "server"
version = "0.1.0"
edition = "2021"
[dependencies]
# Использование общих зависимостей
serde = { workspace = true }
tokio = { workspace = true }
# Локальная зависимость из workspace
shared = { path = "../shared" }
# Дополнительные зависимости
axum = "0.6"
Команды для workspace
# Сборка всех пакетов
cargo build --workspace
# Сборка конкретного пакета
cargo build -p server
# Тестирование всех пакетов
cargo test --workspace
# Выпуск всех пакетов в release
cargo build --workspace --release
# Просмотр дерева зависимостей
cargo tree
# Обновление всех зависимостей
cargo update
Профили сборки
Профили позволяют настроить параметры компиляции:
- Встроенные профили
- Пользовательские профили
- Для отдельных пакетов
# Отладочный профиль (cargo build)
[profile.dev]
opt-level = 0 # Без оптимизаций (быстрая компиляция)
debug = true # Включить отладочную информацию
split-debuginfo = 'unpacked' # Формат отладочной информации
debug-assertions = true # Включить проверки assert!
overflow-checks = true # Проверка переполнения целых чисел
lto = false # Отключить Link Time Optimization
panic = 'unwind' # Стратегия обработки panic
incremental = true # Включить инкрементальную компиляцию
codegen-units = 256 # Количество единиц кодогенерации
# Релизный профиль (cargo build --release)
[profile.release]
opt-level = 3 # Максимальные оптимизации
debug = false # Отключить отладочную информацию
debug-assertions = false # Отключить проверки assert!
overflow-checks = false # Отключить проверки переполнения
lto = false # Link Time Optimization
panic = 'unwind' # Стратегия обработки panic
incremental = false # Отключить инкрементальную компиляцию
codegen-units = 16 # Меньше единиц для лучшей оптимизации
# Профиль для бенчмарков
[profile.bench]
opt-level = 3
debug = false
rpath = false
lto = true
debug-assertions = false
codegen-units = 1
# Профиль для тестирования производительности
[profile.profiling]
inherits = "release" # Наследовать от release
debug = 1 # Частичная отладочная информация
strip = false # Не удалять символы
# Минимальный размер исполняемого файла
[profile.min-size]
inherits = "release"
opt-level = 'z' # Оптимизация размера
lto = true
panic = 'abort' # Прерывание вместо разворачивания стека
strip = true # Удалить отладочные символы
Использование пользовательских профилей:
cargo build --profile=profiling
cargo build --profile=min-size
# Оптимизировать конкретные зависимости даже в debug режиме
[profile.dev.package.image]
opt-level = 2
[profile.dev.package.serde]
opt-level = 3
# Настройки для всех зависимостей
[profile.dev.package."*"]
opt-level = 1
debug = false
Скрипты сборки (build.rs)
Скрипты сборки выполняются перед компиляцией основного кода:
Создание build.rs
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::Path;
fn main() {
// Переменные окружения от Cargo
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("hello.rs");
// Генерация кода
let mut f = File::create(&dest_path).unwrap();
f.write_all(b"
pub fn generated_hello() {
println!(\"Hello from generated code!\");
}
").unwrap();
// Указываем Cargo перекомпилировать при изменении файлов
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=src/");
// Устанавливаем переменные окружения для главного кода
println!("cargo:rustc-env=BUILD_TIME={}", chrono::Utc::now());
// Передаём флаги компилятору
println!("cargo:rustc-link-lib=static=mylib");
println!("cargo:rustc-link-search=native=/path/to/lib");
}
Использование в main.rs
// Подключение сгенерированного кода
include!(concat!(env!("OUT_DIR"), "/hello.rs"));
fn main() {
generated_hello();
// Использование переменной из build.rs
println!("Собрано в: {}", env!("BUILD_TIME"));
}
Зависимости для build.rs
[package]
name = "my_app"
version = "0.1.0"
edition = "2021"
build = "build.rs" # Указываем скрипт сборки
[build-dependencies]
chrono = { version = "0.4", features = ["serde"] }
cc = "1.0" # Для компиляции C кода
bindgen = "0.60" # Для генерации биндингов
Продвинутые команды Cargo
Информационные команды
# Информация о проекте и зависимостях
cargo metadata --format-version=1 | jq
# Дерево зависимостей
cargo tree
# Дерево с конкретными функциями
cargo tree --features="json xml"
# Обратные зависимости
cargo tree --invert serde
# Дублирующиеся зависимости
cargo tree --duplicates
# Размеры в скомпилированном файле
cargo bloat --release --crates
# Время компиляции зависимостей
cargo build --timings
Команды очистки и обслуживания
# Очистка папки target
cargo clean
# Очистка только release артефактов
cargo clean --release
# Очистка конкретного пакета в workspace
cargo clean -p my_package
# Проверка неиспользуемых зависимостей
cargo machete
# Обновление зависимостей
cargo update
# Обновление конкретной зависимости
cargo update -p serde
# Аудит безопасности зависимостей
cargo audit
Команды тестирования
# Тестирование с выводом результатов
cargo test -- --nocapture
# Параллельное выполнение тестов
cargo test -- --test-threads=4
# Тестирование конкретного теста
cargo test test_name
# Тестирование с конкретными функциями
cargo test --features="json xml"
# Тестирование документации
cargo test --doc
# Бенчмарки (только на nightly)
cargo +nightly bench
Публикация пакетов
Подготовка к публикации
# Сухой запуск публикации
cargo publish --dry-run
# Проверка пакета
cargo package
# Просмотр содержимого пакета
cargo package --list
# Локальная установка для тестирования
cargo install --path .
Настройка публикации
[package]
name = "my_awesome_lib"
version = "1.0.0"
edition = "2021"
authors = ["Your Name <email@example.com>"]
description = "Краткое описание библиотеки"
license = "MIT OR Apache-2.0"
repository = "https://github.com/username/my_awesome_lib"
documentation = "https://docs.rs/my_awesome_lib"
homepage = "https://my-awesome-lib.example.com"
readme = "README.md"
keywords = ["parser", "json", "data"] # До 5 ключевых слов
categories = ["parsing", "data-structures"] # Категории с crates.io
# Исключить ненужные файлы из публикации
exclude = [
"tests/fixtures/*",
"*.tmp",
"docs/internal/*"
]
# Или включить только нужные
include = [
"src/**/*",
"Cargo.toml",
"README.md",
"LICENSE*"
]
Публикация
# Авторизация в crates.io (один раз)
cargo login YOUR_API_TOKEN
# Публикация
cargo publish
# Отзыв версии (только в течение 72 часов)
cargo yank --vers 1.0.0
# Отмена отзыва
cargo yank --vers 1.0.0 --undo
Заключение
В этой главе мы детально изучили Cargo:
✅ Типы проектов — бинарные, библиотечные, смешанные ✅ Глубокую структуру Cargo.toml — все секции и возможности ✅ Управление зависимостями — источники, версии, функции ✅ Рабочие области — организацию больших проектов ✅ Профили сборки — оптимизацию компиляции ✅ Скрипты сборки — кодогенерацию и интеграцию ✅ Продвинутые команды — для профессиональной разработки
Cargo — это мощный инструмент, который делает разработку на Rust продуктивной и приятной. Понимание его возможностей критически важно для эффективной работы с языком.
В следующей главе: "Переменные и мутабельность" — мы начнём изучать основы синтаксиса Rust, начиная с переменных и понятия изменяемости.
Практические задания
- Создайте workspace с библиотекой и двумя приложениями, использующими эту библиотеку
- Настройте пользовательские features для условной компиляции разных функций
- Создайте build.rs скрипт, генерирующий код на основе переменных окружения
- Оптимизируйте профили сборки для минимального размера исполняемого файла
Вопросы для самопроверки
- В чём разница между
[dependencies],[dev-dependencies]и[build-dependencies]? - Как работают workspace и какие преимущества они дают?
- Что такое features и как они влияют на размер скомпилированного кода?
- Когда нужны скрипты сборки (build.rs)?
Полезные ссылки
- The Cargo Book
- Crates.io — официальный реестр пакетов
- Docs.rs — документация для всех пакетов
- Cargo.toml Reference